
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using Infrastructure.Managers;
using Infrastructure.ObjectModel.Animations;

namespace Infrastructure.ObjectModel
{
    public delegate void ColidedEventHandler(object i_Collidable);

	public class Sprite : LoadableDrawableComponent
	{

        protected AnimationsManager m_Animations;

        public AnimationsManager Animations
        {
            get { return m_Animations; }
            set { m_Animations = value; }
        }


        private bool m_UseSharedBatch = false;

		protected SpriteBatch m_SpriteBatch;

        public SpriteBatch SpriteBatch
        {
            set
            {
                m_SpriteBatch = value;
                m_UseSharedBatch = true;
            }
        }

		private Texture2D m_Texture;

		public Texture2D Texture
		{
			get { return m_Texture; }
			set 
            { 
                m_Texture = value;
                m_TextureData = new Color[m_Texture.Width * m_Texture.Height];
                m_Texture.GetData<Color>(m_TextureData);
                updateSourceRectTexture();
            }
		}

        protected Color[] m_TextureData;
        protected Color[] m_TexturePerSourceRect;

        public Color[] TextureData
        {
            get
            {
                if (SourceRectangle != null && SourceRectangle != Rectangle.Empty)
                {
                    if (m_TexturePerSourceRect == null)
                    {
                        updateSourceRectTexture();
                    }
                    return m_TexturePerSourceRect;
                }
                else
                {
                    return m_TextureData;
                }                
            }
            set
            {
                m_TextureData = value;

                if (SourceRectangle != null)
                {
                    updateSourceRectTexture();
                }
            }
        }

        protected void updateSourceRectTexture()
        {
            if (m_TextureData != null && SourceRectangle != Rectangle.Empty)
            {
                Rectangle i_rectangleA = new Rectangle(0, 0, Texture.Width, Texture.Height);
                Rectangle i_rectangleB = SourceRectangle;
                m_TexturePerSourceRect = new Color[SourceRectangle.Width * SourceRectangle.Height];

                // Find the bounds of the rectangle intersection
                int top = Math.Max(i_rectangleA.Top, i_rectangleB.Top);
                int bottom = Math.Min(i_rectangleA.Bottom, i_rectangleB.Bottom);
                int left = Math.Max(i_rectangleA.Left, i_rectangleB.Left);
                int right = Math.Min(i_rectangleA.Right, i_rectangleB.Right);

                // Check every point within the intersection bounds
                for (int y = top; y < bottom; y++)
                {
                    for (int x = left; x < right; x++)
                    {
                        // Get the color of both pixels at this point
                        m_TexturePerSourceRect[(x - i_rectangleB.Left) +
                                             (y - i_rectangleB.Top) * i_rectangleB.Width] = m_TextureData[(x - i_rectangleA.Left) +
                                             (y - i_rectangleA.Top) * i_rectangleA.Width];
                    }
                }
            }
        }

        public int Width
        {
            get 
            {
                return (int)WidthAfterScale; 
            }
        }

        public int Height
        {
            get { return (int)HeightAfterScale; }
        }

        // TODO 01: The Bounds property for collision detection
        public Rectangle Bounds
        {
            get
            {
                return new Rectangle(
                    (int)TopLeftPosition.X,
                    (int)TopLeftPosition.Y,
                    Width,
                    Height);
            }
        }
        // -- end of TODO 01

        public float WidthAfterScale
        {
            get { return m_WidthBeforeScale * m_Scale; }
            set { m_WidthBeforeScale = value / m_Scale; }
        }

        public float HeightAfterScale
        {
            get { return m_HeightBeforeScale * m_Scale; }
            set { m_HeightBeforeScale = value / m_Scale; }
        }

        protected float m_WidthBeforeScale;
        public float WidthBeforeScale
        {
            get { return m_WidthBeforeScale; }
            set { m_WidthBeforeScale = value; }
        }

        protected float m_HeightBeforeScale;
        public float HeightBeforeScale
        {
            get { return m_HeightBeforeScale; }
            set { m_HeightBeforeScale = value; }
        }

        protected Vector2 m_PositionOfOrigin = Vector2.Zero;
        /// <summary>
        /// Represents the location of the sprite's origin point in screen coorinates
        /// </summary>
        public Vector2 PositionOfOrigin
        {
            get { return m_PositionOfOrigin; }
            set
            {
                if (m_PositionOfOrigin != value)
                {
                    m_PositionOfOrigin = value;
                    OnPositionChanged();
                }
            }
        }

        public Vector2 m_PositionOrigin;
        public Vector2 PositionOrigin
        {
            get { return m_PositionOrigin; }
            set { m_PositionOrigin = value; }
        }

        public Vector2 m_RotationOrigin = Vector2.Zero;
        public Vector2 RotationOrigin
        {
            get { return m_RotationOrigin; }// r_SpriteParameters.RotationOrigin; }
            set { m_RotationOrigin = value; }
        }

        public Vector2 PositionForDraw
        {
            get { return this.PositionOfOrigin - this.PositionOrigin + this.RotationOrigin; }
        }

        public Vector2 TopLeftPosition
        {
            get { return this.PositionOfOrigin - this.PositionOrigin; }
            set { this.PositionOfOrigin = value + this.PositionOrigin; }
        }

        public Rectangle ScreenBoundsAfterScale
        {
            get
            {
                return new Rectangle(
                    (int)TopLeftPosition.X,
                    (int)TopLeftPosition.Y,
                    (int)this.WidthAfterScale,
                    (int)this.HeightAfterScale);
            }
        }

        public Rectangle ScreenBoundsBeforeScale
        {
            get
            {
                return new Rectangle(
                    (int)TopLeftPosition.X,
                    (int)TopLeftPosition.Y,
                    (int)this.WidthBeforeScale,
                    (int)this.HeightBeforeScale);
            }
        }

        public virtual Rectangle ColliadbleBounds
        {
            get { return ScreenBoundsAfterScale; }
        }

        protected Rectangle m_SourceRectangle;
        public Rectangle SourceRectangle
        {
            get { return m_SourceRectangle; }
            set 
            { 
                m_SourceRectangle = value;
                m_HeightBeforeScale = m_SourceRectangle.Height;
                m_WidthBeforeScale = m_SourceRectangle.Width;
                updateSourceRectTexture();
            }
        }

        public Vector2 TextureCenter
        {
            get
            {
                return new Vector2((float)(m_Texture.Width / 2), (float)(m_Texture.Height / 2));
            }
        }

        public Vector2 SourceRectangleCenter
        {
            get { return new Vector2((float)(m_SourceRectangle.Width / 2), (float)(m_SourceRectangle.Height / 2)); }
        }

        protected float m_Rotation = 0;
        public float Rotation
        {
            get { return m_Rotation; }
            set { m_Rotation = value; }
        }

        protected float m_Scale = 1;
        public float Scale
        {
            get { return m_Scale; }
            set { m_Scale = value; }
        }

	
		protected Color m_TintColor = Color.White;

		public Color TintColor
		{
			get { return m_TintColor; }
			set 
            {
                if (m_TintColor != value)
                {
                    m_TintColor = value;
                    UpdateOpacity();
                }
            }
		}


        private int m_Opacity = 100;
        public int Opacity
        {
            get { return m_Opacity; }
            set
            {
                if (m_Opacity != value)
                {
                    m_Opacity = value;
                    UpdateOpacity();
                }
            }
        }

        protected Vector2 m_Velocity = Vector2.Zero;
        /// <summary>
        /// Pixels per Second on 2 axis
        /// </summary>
        public Vector2 Velocity
        {
            get { return m_Velocity; }
            set { m_Velocity = value; }
        }

        private float m_AngularVelocity = 0;
        /// <summary>
        /// Radians per Second on X Axis
        /// </summary>
        public float AngularVelocity
        {
            get { return m_AngularVelocity; }
            set { m_AngularVelocity = value; }
        }

        private void UpdateOpacity()
        {
            // The A component of the rgbA si a float between 0 and 1,
            // while 1 is not opacity at all

            Vector4 tintColor = m_TintColor.ToVector4();
            tintColor.W = (float)m_Opacity / 100f;
            m_TintColor = new Color(tintColor);
        }

        public Sprite(string i_AssetName, Game i_Game, int i_UpdateOrder, int i_DrawOrder)
            : base(i_AssetName, i_Game, i_UpdateOrder, i_DrawOrder)
        {
        }

        public Sprite(string i_AssetName, Game i_Game, int i_CallsOrder)
            : base(i_AssetName, i_Game, i_CallsOrder)
        {
        }

        public Sprite(string i_AssetName, Game i_Game)
            : base(i_AssetName, i_Game, int.MaxValue)
        {
        }


        protected override void LoadContent()
        {
            Texture = Game.Content.Load<Texture2D>(m_AssetName);

            if (m_SpriteBatch == null)
            {
                m_SpriteBatch =
                    Game.Services.GetService(typeof(SpriteBatch)) as SpriteBatch;

                if (m_SpriteBatch == null)
                {
                    m_SpriteBatch = new SpriteBatch(Game.GraphicsDevice);
                    m_UseSharedBatch = false;
                }
            }

            base.LoadContent();
        }
        /// <summary>
        /// Default initialization of bounds
        /// </summary>
        /// <remarks>
        /// Derived classes are welcome to override this to implement their specific boudns initialization
        /// </remarks>
        protected override void InitBounds()
        {
            m_WidthBeforeScale = m_Texture.Width;
            m_HeightBeforeScale = m_Texture.Height;

            InitSourceRectangle();
            InitOrigins();
        }

        protected virtual void InitOrigins()
        {
        }

        protected virtual void InitSourceRectangle()
        {
            m_SourceRectangle = new Rectangle(0, 0, (int)m_WidthBeforeScale, (int)m_HeightBeforeScale);
        }

        /// <summary>
        /// Basic movement logic (position += velocity * totalSeconds)
        /// </summary>
        /// <param name="gameTime"></param>
        /// <remarks>
        /// Derived classes are welcome to extend this logic.
        /// </remarks>
        public override void Update(GameTime gameTime)
        {
            float totalSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

            this.PositionOfOrigin += this.Velocity * totalSeconds;
            this.Rotation += this.AngularVelocity * totalSeconds;

            base.Update(gameTime);

            this.Animations.Animate(gameTime);
        }

        public override void Initialize()
        {
            base.Initialize();

            m_Animations = new AnimationsManager(this);
        }

        /// <summary>
        /// Basic texture draw behavior, using a shared/own sprite batch
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            if (!m_UseSharedBatch)
            {
                m_SpriteBatch.Begin();
            }

            m_SpriteBatch.Draw(m_Texture, this.PositionForDraw,
                 this.SourceRectangle, this.TintColor,
                this.Rotation, this.RotationOrigin, this.Scale,
                SpriteEffects.None, this.LayerDepth);

            if (!m_UseSharedBatch)
            {
                m_SpriteBatch.End();
            }

            base.Draw(gameTime);
        }


        protected float m_LayerDepth;

        public float LayerDepth
        {
            get { return m_LayerDepth ; }
            set { m_LayerDepth = value; }
        }


        // TODO 04:
        protected override void DrawBoundingBox()
        {
            // not implemented yet
        }
        // -- end of TODO 04

        // TODO 14: Implement a basic collision detection between two ICollidable2D objects:
        public virtual bool CheckCollision(ICollidable i_Source)
        {
            bool collided = false;
            ICollidable2D source = i_Source as ICollidable2D;
            if (source != null) 
            {
                if (source.Bounds.Intersects(this.Bounds))
                {
                    // Check who has smaller rect to check on
                    collided = intersectPixels(this.Bounds, this.TextureData, source.Bounds, source.TextureData);
                }
            }

            return collided;
        }
        // -- end of TODO 14

        /// <summary>
        /// Determines if there is overlap of the non-transparent pixels
        /// between two sprites.
        /// </summary>
        /// <param name="rectangleA">Bounding rectangle of the first sprite</param>
        /// <param name="dataA">Pixel data of the first sprite</param>
        /// <param name="rectangleB">Bouding rectangle of the second sprite</param>
        /// <param name="dataB">Pixel data of the second sprite</param>
        /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
        private bool intersectPixels(Rectangle i_rectangleA, Color[] i_dataA,
                                    Rectangle i_rectangleB, Color[] i_dataB)
        {
            bool retVal = false;

            // Find the bounds of the rectangle intersection
            int top = Math.Max(i_rectangleA.Top, i_rectangleB.Top);
            int bottom = Math.Min(i_rectangleA.Bottom, i_rectangleB.Bottom);
            int left = Math.Max(i_rectangleA.Left, i_rectangleB.Left);
            int right = Math.Min(i_rectangleA.Right, i_rectangleB.Right);

            // Check every point within the intersection bounds
            for (int y = top; y < bottom && !retVal; y++)
            {
                for (int x = left; x < right; x++)
                {
                    // Get the color of both pixels at this point
                    Color colorA = i_dataA[(x - i_rectangleA.Left) +
                                         (y - i_rectangleA.Top) * i_rectangleA.Width];
                    Color colorB = i_dataB[(x - i_rectangleB.Left) +
                                         (y - i_rectangleB.Top) * i_rectangleB.Width];

                    // If both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                    {
                        // then an intersection has been found
                        retVal = true;
                        break;
                    }
                }
            }

            // No intersection found
            return retVal;
        }

        // TODO 15: Implement a basic collision reaction between two ICollidable2D objects
        public virtual void Collided(ICollidable i_Collidable)
        {
            // defualt behavior - change direction:
            this.Velocity *= -1;
        }

        public void Distroy()
        {
            this.Enabled = false;
            this.Visible = false;
            this.Game.Components.Remove(this);
        }

        public void Shrink(GameTime i_elapsedTime, double i_CompleteShrinkMiliseconds)
        {
            this.Scale = this.Scale - ((float)(i_elapsedTime.ElapsedGameTime.TotalMilliseconds / 
                                               i_CompleteShrinkMiliseconds));
        }

        public void SpinAndDisappear(int i_RotationSpeed, int i_DisappearSpeed)
        {
            this.Rotation += i_RotationSpeed;
            this.Opacity -= i_DisappearSpeed;
        }


        public Sprite ShallowClone()
        {
            return this.MemberwiseClone() as Sprite;
        }
    }
}